--[[

  sample implementation of infix to RPN parser and RPN compute engine.

]]--

-- this function recursively flattens the parsed
-- RPN expression to yield a single linear list
-- of compute elements
function flatten(parseTree, list) 

  for _, item in ipairs(parseTree) do

    if (type(item) == "string") then

      -- we convert string elements representing numbers into
      -- floats right away so we don't have to do it during
      -- runtime
      local isNumber = tonumber(item)
      if isNumber then
        list[#list+1] = isNumber
      else
        list[#list+1] = item
      end
    elseif (type(item) == "table") then
      flatten(item, list)
    else
      error("Unknown RPN element")
    end

  end

  return list
end


-- this function computes the value of a passed 
-- math expression in RPN format
function compute_rpn(expression, variables, molcounts)

  local stack = {}
  for _, item in ipairs(expression) do

    if (type(item) == "number") then
      table.insert(stack, item)
    elseif (variables[item] ~= nil) then
      table.insert(stack, (variables[item])[1])
    elseif (molcounts[item] ~= nil) then
      table.insert(stack, molcounts[item].count)
    elseif (item == "sin") then
      local num = table.remove(stack)
      table.insert(stack, math.sin(num))
    elseif (item == "cos") then
      local num = table.remove(stack)
      table.insert(stack, math.cos(num))
    elseif (item == "tan") then
      local num = table.remove(stack)
      table.insert(stack, math.tan(num))
    elseif (item == "exp") then
      local num = table.remove(stack)
      table.insert(stack, math.exp(num))
    elseif (item == "ln") then
      local num = table.remove(stack)
      table.insert(stack, math.log(num))
    elseif (item == "log") then
      local num = table.remove(stack)
      table.insert(stack, math.log10(num))
    elseif (item == "sqrt") then
      local num = table.remove(stack)
      table.insert(stack, math.sqrt(num))
    else
      local snd = table.remove(stack)
      local fst = table.remove(stack)

      if (item == "+") then
        table.insert(stack, fst + snd)
      elseif (item == "-") then
        table.insert(stack, fst - snd)
      elseif (item == "*") then
        table.insert(stack, fst * snd)
      elseif (item == "/") then
        table.insert(stack, fst / snd)
      elseif (item == "^") then
        table.insert(stack, fst ^ snd)
      else
        return nil
        -- error("ERROR: Unknown operator: " .. tostring(item))
      end
    end
    -- print(stack)
  end

  -- at this stage the stack can only contain a single element
  assert(#stack == 1)

  return stack[1]
end



rpn_engine = {
  compute_rpn = compute_rpn,
  flatten = flatten
}

return rpn_engine
